#include <aphw/Environment.hpp>

#include <vector>

#include <boost/foreach.hpp>
#include <boost/python.hpp>
#include <ham/hamsterdb.h>

#include <aphw/Database.hpp>
#include <aphw/Exceptions.hpp>
#include <aphw/Utils.hpp>

using namespace boost::python;

namespace
{

ham_status_t open_env(aphw::Environment& env,
                      const std::string& filename,
                      ham_u32_t flags,
                      dict params)
{
    std::vector<ham_parameter_t> paramvec =
        aphw::constructParams(params);

    return ham_env_open_ex(env.env(),
                           filename.c_str(),
                           flags,
                           &paramvec[0]);
}

ham_status_t create_env(aphw::Environment& env,
                        const std::string& filename,
                        ham_u32_t flags,
                        dict params)
{
    std::vector<ham_parameter_t> paramvec =
        aphw::constructParams(params);

    return ham_env_create_ex(env.env(),
                             filename.c_str(),
                             flags,
                             S_IRWXU | S_IRWXG,
                             &paramvec[0]);
}

ham_status_t create_env_memory(
    aphw::Environment& env,
    ham_u32_t flags,
    dict params) 
{
    std::vector<ham_parameter_t> paramvec =
        aphw::constructParams(params);

    return ham_env_create_ex(
        env.env(),
        NULL,
        flags,
        S_IRWXU | S_IRWXG,
        &paramvec[0]);
}

ham_status_t close_env(aphw::Environment& env,
                       ham_u32_t flags)
{
    return ham_env_close(env.env(),
                         flags);
}

tuple create_db(aphw::Environment& env,
                ham_u16_t name,
                ham_u32_t flags)
{
    aphw::Database db;
    
    ham_parameter_t params[]={
        { 0, 0 }
    };
    
    ham_status_t err = 
        ham_env_create_db(env.env(),
                          db.db(),
                          name,
                          flags,
                          params);
    
    return make_tuple(err, db);
}

tuple open_db(aphw::Environment& env,
              ham_u16_t name,
              ham_u32_t flags)
{
    aphw::Database db;

    ham_parameter_t params[]={
        { 0, 0 }
    };

    ham_status_t err =  
        ham_env_open_db(env.env(), 
                        db.db(), 
                        name, 
                        flags, 
                        params);

    return make_tuple(err, db);
}

ham_status_t rename_db(aphw::Environment& env,
                       ham_u16_t oldname,
                       ham_u16_t newname,
                       ham_u32_t flags)
{
    return ham_env_rename_db(env.env(), 
                             oldname, 
                             newname, 
                             flags);
}

ham_status_t erase_db(aphw::Environment& env,
                      ham_u16_t name,
                      ham_u32_t flags)
{
    return ham_env_erase_db(env.env(), 
                            name, 
                            flags);
}

ham_status_t enable_encryption(aphw::Environment& env,
                               object pykey,
                               ham_u32_t flags)
{
    const static std::size_t KEY_SIZE=16;

    ham_u8_t key[KEY_SIZE];

    for (int i = 0; i < KEY_SIZE; ++i)
    {
        key[i] = extract<ham_u8_t>(pykey[i]);
    }
    
    return ham_env_enable_encryption(env.env(), 
                                     key, 
                                     flags);
}

tuple database_names(aphw::Environment& env)
{
    std::vector<ham_u16_t> names;

    while (true)
    {
        names.resize(names.size() + 100);
        ham_size_t count = names.size();

        ham_status_t err =
            ham_env_get_database_names(env.env(), 
                                       &names[0],
                                       &count);

        if (err == HAM_SUCCESS)
        {
            list rval;

            BOOST_FOREACH(ham_u16_t name, names)
            {
                rval.append(name);
            }

            return make_tuple(err, rval);
        }
        else if (err != HAM_LIMITS_REACHED)
        {
            return make_tuple(err, object());
        }
    }
}

}

namespace aphw
{

struct Environment::Body
{
    Body() :
        env_ (NULL)
        {
            ham_status_t err = ham_env_new(&env_);
            if (HAM_OUT_OF_MEMORY == err)
                throw OutOfMemoryError();

            assert(env_);
        }

    ~Body()
        {
            assert(env_);
            ham_status_t err = ham_env_delete(env_);
            assert(HAM_SUCCESS == err);
        }

    ham_env_t* env_;
};

Environment::Environment() :
    body_ (new Body())
{}

ham_env_t* Environment::env()
{
    assert(body_);
    assert(body_->env_);
    return body_->env_;
}

void init_Environment()
{
    class_<Environment>("Environment", init<>())
        ;

    def("open_env", ::open_env);
    def("create_env", ::create_env);
    def("create_env_memory", ::create_env_memory);
    def("close_env", ::close_env);
    def("create_db_env", ::create_db);
    def("open_db_env", ::open_db);
    def("rename_db_env", ::rename_db);
    def("erase_db_env", ::erase_db);
    def("database_names_env", ::database_names);
    def("enable_encryption_env", ::enable_encryption);
}

}
